#include  "wx/wxprec.h"

#if !wxUSE_FILE || !wxUSE_TEXTBUFFER
#undef wxUSE_TEXTFILE
#define wxUSE_TEXTFILE 0
#endif // wxUSE_FILE

#if wxUSE_TEXTFILE

#include "wx/textfile.h"
#include "wx/filename.h"
#include "wx/buffer.h"

wxTextFile::wxTextFile( const wxString& strFileName )
  : wxTextBuffer( strFileName ) {
}

bool wxTextFile::OnExists() const {
  return wxFile::Exists( m_strBufferName );
}


bool wxTextFile::OnOpen( const wxString &strBufferName, wxTextBufferOpenMode OpenMode ) {
  wxFile::OpenMode FileOpenMode;
  switch( OpenMode ) {
    default:
      wxFAIL_MSG( _T( "unknown open mode in wxTextFile::Open" ) );
    case ReadAccess :
      FileOpenMode = wxFile::read;
      break;
    case WriteAccess :
      FileOpenMode = wxFile::write;
      break;
  }
  return m_file.Open( strBufferName.c_str(), FileOpenMode );
}

bool wxTextFile::OnClose() {
  return m_file.Close();
}

bool wxTextFile::OnRead( const wxMBConv& conv ) {
  wxASSERT_MSG( m_file.IsOpened(), _T( "can't read closed file" ) );
  size_t bufSize = 0, bufPos = 0;
  char block[1024];
  wxCharBuffer buf;
  wxFileOffset fileLength;
  {
    wxLogNull logNull;
    fileLength = m_file.Length();
  }
  const bool seekable = fileLength != wxInvalidOffset && fileLength != 0;
  if( seekable ) {
    // we know the required length, so set the buffer size in advance
    bufSize = fileLength;
    if( !buf.extend( bufSize - 1 ) ) {
      return false;
    }
    wxASSERT_MSG( m_file.Tell() == 0, _T( "should be at start of file" ) );
  }
  for( ;; ) {
    ssize_t nRead = m_file.Read( block, WXSIZEOF( block ) );
    if( nRead == wxInvalidOffset ) {
      return false;
    }
    if( nRead == 0 ) {
      if( bufPos == 0 ) {
        return true;
      }
      // otherwise we've finished reading the file
      break;
    }
    if( seekable ) {
      wxCHECK_MSG( bufPos + nRead <= bufSize, false,
                   _T( "read more than file length?" ) );
    } else { // !seekable
      if( !buf.extend( bufPos + nRead - 1 ) ) {
        return false;
      }
    }
    // append to the buffer
    memcpy( buf.data() + bufPos, block, nRead );
    bufPos += nRead;
  }
  if( !seekable ) {
    bufSize = bufPos;
  }
  const wxString str( buf, conv, bufPos );
  if( bufSize > 4 && str.empty() ) {
    wxLogError( _( "Failed to convert file \"%s\" to Unicode." ), GetName() );
    return false;
  }
  free( buf.release() ); // we don't need this memory any more
  wxChar chLast = '\0';
  wxString::const_iterator lineStart = str.begin();
  const wxString::const_iterator end = str.end();
  for( wxString::const_iterator p = lineStart; p != end; p++ ) {
    const wxChar ch = *p;
    switch( ch ) {
      case '\n':
        if( chLast == '\r' ) {
          if( p - 1 >= lineStart ) {
            AddLine( wxString( lineStart, p - 1 ), wxTextFileType_Dos );
          } else {
            // there were two line endings, so add an empty line:
            AddLine( wxEmptyString, wxTextFileType_Dos );
          }
        } else // bare '\n', Unix style
        { AddLine( wxString( lineStart, p ), wxTextFileType_Unix ); }
        lineStart = p + 1;
        break;
      case '\r':
        if( chLast == '\r' ) {
          if( p - 1 >= lineStart ) {
            AddLine( wxString( lineStart, p - 1 ), wxTextFileType_Mac );
          }
          // Mac empty line
          AddLine( wxEmptyString, wxTextFileType_Mac );
          lineStart = p + 1;
        }
        //else: we don't know what this is yet -- could be a Mac EOL or
        //      start of DOS EOL so wait for next char
        break;
      default:
        if( chLast == '\r' ) {
          // Mac line termination
          if( p - 1 >= lineStart ) {
            AddLine( wxString( lineStart, p - 1 ), wxTextFileType_Mac );
          } else {
            // there were two line endings, so add an empty line:
            AddLine( wxEmptyString, wxTextFileType_Mac );
          }
          lineStart = p;
        }
    }
    chLast = ch;
  }
  // anything in the last line?
  if( lineStart != end ) {
    // add unterminated last line
    AddLine( wxString( lineStart, end ), wxTextFileType_None );
  }
  return true;
}


bool wxTextFile::OnWrite( wxTextFileType typeNew, const wxMBConv& conv ) {
  wxFileName fn = m_strBufferName;
  // We do NOT want wxPATH_NORM_CASE here, or the case will not
  // be preserved.
  if( !fn.IsAbsolute() )
    fn.Normalize( wxPATH_NORM_ENV_VARS | wxPATH_NORM_DOTS | wxPATH_NORM_TILDE |
                  wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG );
  wxTempFile fileTmp( fn.GetFullPath() );
  if( !fileTmp.IsOpened() ) {
    wxLogError( _( "can't write buffer '%s' to disk." ), m_strBufferName.c_str() );
    return false;
  }
  size_t nCount = GetLineCount();
  for( size_t n = 0; n < nCount; n++ ) {
    fileTmp.Write( GetLine( n ) +
                   GetEOL( typeNew == wxTextFileType_None ? GetLineType( n )
                           : typeNew ),
                   conv );
  }
  // replace the old file with this one
  return fileTmp.Commit();
}

#endif // wxUSE_TEXTFILE
